﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Twodi.Behavior.Interfaces;
using Microsoft.Xna.Framework;
using Twodi.Input;
using Twodi.Utility;
using Twodi.Interfaces;
using Twodi.Input.Events;
using Twodi.Arguments;

namespace Twodi.Behavior.Implemented
{
    /// <summary>
    /// Define um componente com eventos de mouse.
    /// Classe responsavel por conter propriedades e controle destas.
    /// </summary
    public class MouseEventsBehavior : AbstractBehavior<IMouseEventBehavior>, IMouseEvent
    {
        /// <summary>
        /// Houve movimento no mouse enquanto estava com click.
        /// </summary>
        protected bool mouseDrag;

        /// <summary>
        /// Mouse sobre o componente.
        /// </summary>
        protected bool isMouseOver;

        /// <summary>
        /// Algum botão pressionado.
        /// </summary>
        protected bool isMouseDown;

        /// <summary>
        /// Construtor padrão.
        /// </summary>
        /// <param name="component">Componente a ser manipulado.</param>
        public MouseEventsBehavior(IMouseEventBehavior component)
            : base(component)
        {
            component.MouseInput.Events.Add(
                new MouseEvent(MouseEventsType.Anytime, (sender, e) =>
                {
                    if (!Enabled) return true;

                    bool returning = true;

                    InputEventArgs<MouseInput> args = new InputEventArgs<MouseInput>(e.Input);

                    if (isMouseOver)
                    {
                        returning &= onMouseHover(args);
                    }

                    return returning;
                }
            ));

            component.MouseInput.Events.Add(
                new MouseEvent(MouseEventsType.Moved, (sender, e) =>
                {
                    if (!Enabled) return true;

                    bool returning = true;

                    InputEventArgs<MouseInput> args = new InputEventArgs<MouseInput>(e.Input);

                    Vector2 relativeMousePos = Vector2.Transform(e.Input.Position, component.TransformationProperties.Inverted);

                    float normal = 1f / this.Component.ScaleProperties.Scale;

                    //Sobre componente.
                    if (relativeMousePos.X >= 0 && relativeMousePos.X <= this.Component.SizeProperties.Size.X * normal &&
                        relativeMousePos.Y >= 0 && relativeMousePos.Y <= this.Component.SizeProperties.Size.Y * normal)
                    {
                        //E estava como fora.
                        if (!isMouseOver)
                        {
                            isMouseOver = true;
                            returning &= onMouseOver(args);
                        }
                        else
                        {
                            returning &= onMouseMove(args);

                            //Mouse moveu, esquerdo pressionado
                            if (e.Input.LeftPressed && !mouseDrag)
                            {
                                mouseDrag = true;
                                returning &= onMouseDrag(args);
                            }
                        }
                    }
                    //Fora
                    else
                    {
                        //E ta como sobre.
                        if (isMouseOver)
                        {
                            isMouseOver = false;
                            returning &= onMouseOut(args);
                        }
                    }

                    return returning;
                }
            ));

            this.Component.MouseInput.Events.Add(new MouseEvent(MouseEventsType.Pressed, (sender, e) =>
            {
                if (!Enabled) return true;

                bool returning = true;

                InputEventArgs<MouseInput> args = new InputEventArgs<MouseInput>(e.Input);

                //Se fora ou dentro e não houver nenhuma tecla pressionada.
                if (!e.Input.IsAnyButtonPressed)
                {
                    //E estiver com alguma dita como pressionada.
                    if (isMouseDown)
                    {
                        isMouseDown = false;
                        returning &= onMouseUp(args);
                    }

                    //Se estiver como drag.
                    if (mouseDrag)
                    {
                        mouseDrag = false;
                        returning &= onMouseDrop(args);
                    }
                }

                return returning;
            }));

            this.Component.MouseInput.Events.Add(new MouseEvent(MouseEventsType.Pressed, (sender, e) =>
            {
                if (!Enabled) return true;

                bool returning = true;

                InputEventArgs<MouseInput> args = new InputEventArgs<MouseInput>(e.Input);

                if (isMouseOver)
                {
                    //Se não havia nenhuma pressionada.
                    if (!isMouseDown)
                    {
                        isMouseDown = true;
                        returning &= onMouseDown(args);
                    }
                }

                return returning;
            }, MouseButton.Any));

            this.Component.MouseInput.Events.Add(new MouseEvent(MouseEventsType.Click, (sender, e) =>
            {
                if (!Enabled) return true;

                bool returning = true;

                InputEventArgs<MouseInput> args = new InputEventArgs<MouseInput>(e.Input);

                if (isMouseOver)
                {
                    returning &= onMouseClick(args);
                }

                return returning;
            }, MouseButton.Left));

        }

        /// <summary>
        /// Realizar logica de controle do componente.
        /// </summary>
        /// <param name="gameTime">Tempo atual do jogo.</param>
        public override void Update(GameTime gameTime)
        {
            return;
        }

        public override void Initialize()
        {
            return;
        }

        public override void Draw(GameTime gameTime)
        {
            return;
        }

        public override bool Handle(KeyboardInput input)
        {
            throw new NotImplementedException();
        }

        public override bool Handle(MouseInput input)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Evento invocado quando ocorrer Click no componente.
        /// </summary>
        public event InputEventHandler<InputEventArgs<MouseInput>> MouseClick;

        /// <summary>
        /// Evento invocado quando ocorrer o pressionar de qualquer botão do mouse no componente.
        /// </summary>
        public event InputEventHandler<InputEventArgs<MouseInput>> MouseDown;

        /// <summary>
        /// Evento invocado quando ocorrer o soltar de qualquer botão do mouse no componente.
        /// </summary>
        public event InputEventHandler<InputEventArgs<MouseInput>> MouseUp;

        /// <summary>
        /// Evento invocado quando ocorrer movimento do mouse no componente.
        /// </summary>
        public event InputEventHandler<InputEventArgs<MouseInput>> MouseMove;

        /// <summary>
        /// Evento invocado quando mouse ficar sobre o componente.
        /// </summary>
        public event InputEventHandler<InputEventArgs<MouseInput>> MouseOut;

        /// <summary>
        /// Evento invocado quando mouse ficar fora do componente.
        /// </summary>
        public event InputEventHandler<InputEventArgs<MouseInput>> MouseOver;

        /// <summary>
        /// Evento invocado quando mouse mover enquanto é pressionado qualquer botão sobre o componente.
        /// </summary>
        public event InputEventHandler<InputEventArgs<MouseInput>> MouseDrag;

        /// <summary>
        /// Evento invocado quando todos botões do mouse são soltos após um evento de drag.
        /// </summary>
        public event InputEventHandler<InputEventArgs<MouseInput>> MouseDrop;

        /// <summary>
        /// Evento invocado quando mouse permanece sobre o componente após um MouseOver.
        /// </summary>
        public event InputEventHandler<InputEventArgs<MouseInput>> MouseHover;

        /// <summary>
        /// Método invocado no momento que ocorrer click no componente.
        /// </summary>
        /// <param name="e"></param>
        protected bool onMouseClick(InputEventArgs<MouseInput> e)
        {
            if (MouseClick != null)
            {
                return MouseClick(this, e);
            }
            return true;
        }

        /// <summary>
        /// Método invocado no momento que ocorrer o pressionar de qualquer botão do mouse no componente.
        /// </summary>
        /// <param name="e"></param>
        protected bool onMouseDown(InputEventArgs<MouseInput> e)
        {
            if (MouseDown != null)
            {
                return MouseDown(this, e);
            }
            return true;
        }

        /// <summary>
        /// Método invocado no momento que ocorrer o soltar de qualquer botão do mouse no componente.
        /// </summary>
        /// <param name="e"></param>
        protected bool onMouseUp(InputEventArgs<MouseInput> e)
        {
            if (MouseUp != null)
            {
                return MouseUp(this, e);
            }
            return true;
        }

        /// <summary>
        /// Método invocado no momento que ocorrer movimento do mouse no componente.
        /// </summary>
        /// <param name="e"></param>
        protected bool onMouseMove(InputEventArgs<MouseInput> e)
        {
            if (MouseMove != null)
            {
                return MouseMove(this, e);
            }
            return true;
        }

        /// <summary>
        /// Método invocado no momento mouse ficar fora do componente.
        /// </summary>
        /// <param name="e"></param>
        protected bool onMouseOut(InputEventArgs<MouseInput> e)
        {
            if (MouseOut != null)
            {
                return MouseOut(this, e);
            }
            return true;
        }

        /// <summary>
        /// Método invocado no momento mouse ficar sobre o componente.
        /// </summary>
        /// <param name="e"></param>
        protected bool onMouseOver(InputEventArgs<MouseInput> e)
        {
            if (MouseOver != null)
            {
                return MouseOver(this, e);
            }
            return true;
        }

        /// <summary>
        /// Método invocado no momento mouse mover enquanto é pressionado qualquer botão sobre o componente.
        /// </summary>
        /// <param name="e"></param>
        protected bool onMouseDrag(InputEventArgs<MouseInput> e)
        {
            if (MouseDrag != null)
            {
                return MouseDrag(this, e);
            }
            return true;
        }

        /// <summary>
        /// Método invocado no momento mouse ficar sobre o componente.
        /// </summary>
        /// <param name="e"></param>
        protected bool onMouseDrop(InputEventArgs<MouseInput> e)
        {
            if (MouseDrop != null)
            {
                return MouseDrop(this, e);
            }
            return true;
        }

        /// <summary>
        /// Método invocado quando mouse permanece sobre o componente após um MouseOver.
        /// </summary>
        /// <param name="e"></param>
        protected bool onMouseHover(InputEventArgs<MouseInput> e)
        {
            if (MouseHover != null)
            {
                return MouseHover(this, e);
            }
            return true;
        }
    }
}
